<?php
/**
 * Copyright 2012  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * ORE aggregated resource
 * 
 * Represents an aggregated resource in an OAI ORE aggregation.
 */
class escape_repository_OreAggregatedResource extends escape_repository_OreObject
{
	/**
	 * Constructs an ORE Aggregated Resource object.
	 * @param escape_repository_Repository $repository Link to the repository object
	 * @param string $uri The URI for the data object
	 * @param simplerdf_RdfResource $rdfResource Prefetched RDF data for object creation
	 * @param boolean $noIndex If true, do not use the index to fetch the object data
	 * @param boolean $allowDeleted If true, also return deleted objects
	 * @return escape_repository_OreAggregatedResource The ORE Aggregated Resource object
	 */
	public function __construct(&$repository, $uri, $rdfResource = null, $noIndex = false, $allowDeleted = false)
	{
		parent::__construct($repository, $uri, $rdfResource, $noIndex, $allowDeleted);
		
		// perform a type check
		if(!in_array('info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource', $this->fedoraModels))
		{
			throw new escape_repository_exception_TypeMismatch("The specified resource is not a ORE Aggregated Resource");
		}
	}
	
	/**
	 * Deletes all relations from other objects to this object.
	 */
	protected function deleteRelationsToThisObject()
	{
		// fetch all incoming relations
		$incomingStatementRdfDocument = $this->getIncomingStatements();
		
		$relationAnnotationUris = array();
		$incomingStatementRdfResources = $incomingStatementRdfDocument->getResources();
		foreach($incomingStatementRdfResources as $incomingStatementRdfResource)
		{
			/* @var $incomingStatementRdfResource simplerdf_RdfResource */
			$predicates = $incomingStatementRdfResource->getPredicates();
			foreach($predicates as $predicate)
			{
				if($predicate === "http://purl.utwente.nl/ns/escape-annotations.owl#subject" || $predicate === "http://purl.utwente.nl/ns/escape-annotations.owl#object")
				{
					$relationAnnotationUris[] = $incomingStatementRdfResource->getSubject();
				}
			}
		}
		
		foreach($incomingStatementRdfResources as $incomingStatementRdfResource)
		{
			/* @var $incomingStatementRdfResource simplerdf_RdfResource */
			$predicates = $incomingStatementRdfResource->getPredicates();
			foreach($predicates as $predicate)
			{
				$subjectUri = $incomingStatementRdfResource->getSubject();
				$oreObject = $this->repository->getOreObjectByUri($subjectUri);
				
				$rdfResource = $oreObject->getProperties();
				foreach($predicates as $predicate)
				{
					if($predicate !== "http://purl.utwente.nl/ns/escape-annotations.owl#subject" && $predicate !== "http://purl.utwente.nl/ns/escape-annotations.owl#object")
					{
						$rdfResource->removeStatement($predicate,  $this->getUri());
					}
				}
				$oreObject->setProperties($rdfResource);
			}
		}
		
		foreach($relationAnnotationUris as $relationAnnotationUri)
		{
			$subjectUri = $relationAnnotationUri;
			$oreObject = $this->repository->getOreObjectByUri($subjectUri);
			$oreObject->delete();
		}
	}

	/**
	 * Deletes the current object and all relations to it (sets the object state to 'Deleted').
	 * @return string Timestamp
	 */
	public function delete()
	{
		try
		{
			$this->deleteRelationsToThisObject();
		}
		catch(escape_repository_exception_NotFound $exception)
		{
			// ignore
		}
		
		return parent::delete();
	}

	/**
	 * Purges the current object and removes it from the aggregation.
	 * @return string Timestamp
	 */
	public function purge()
	{
		$aggregation = $this->getAggregation();
		
		if($aggregation !== null)
		{
			// remove it from the aggregation
			$rdfResource = $aggregation->getProperties();
			$rdfResource->removeStatement("http://www.openarchives.org/ore/terms/aggregates", $this->uri);
			$aggregation->setProperties($rdfResource);
		}
		
		return parent::purge();
	}

	/**
	 * Fetches the aggregation to which this aggregated resource belongs.
	 * @return escape_repository_OreAggregation The aggregation to which this aggregated resource belongs
	 * @throws escape_repository_exception_NotFound
	 */
	public function &getAggregation()
	{
		$aggregation = null;
		
		//TODO cache?
		
		//TODO: prefetch whole object in the same query? 
		$query = "select ?uri where { ?uri <http://www.openarchives.org/ore/terms/aggregates> <" . $this->uri . "> }";
		$sparqlResultSet = $this->tripleStore->sparqlSelect($query);
		
		$results = $sparqlResultSet->getResults();
		if(count($results) > 0)
		{
			$aggregationUri = $results[0]['uri']['value'];
			$aggregation = $this->repository->getOreAggregationByUri($aggregationUri);
		}
		
		if($aggregation === null)
		{
			throw new escape_repository_exception_NotFound('Unable to get the aggregation to which this aggregated resource belongs.');
		}
		
		return $aggregation;
	}

	/**
	 * Get the title for this data objec using the title URIs specified in the repository schema.
	 * Overridden to automatically generate titles for relation annotations.
	 * @param boolean $returnAsTriple If true, return the title as a triple array
	 * @return string|array Title as a string or array with the keys 's', 'P' and 'o'
	 */
	public function getTitle($returnAsTriple = false)
	{
		if($this->titleTriple === null && $this->hasRdfType('http://purl.utwente.nl/ns/escape-annotations.owl#RelationAnnotation'))
		{
			$rdfResource = $this->getProperties();
			
			$relationSubjectUri = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#subject');
			$relationPredicateUri = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#predicate');
			$relationObjectUri = $rdfResource->getFirstObject('http://purl.utwente.nl/ns/escape-annotations.owl#object');
			
			$title = null;
			if($relationSubjectUri === null || $relationPredicateUri === null || $relationObjectUri === null)
			{
				$title = 'ERROR: Invalid Relation Annotation object';
			}
			else
			{
				$relationSubject = $this->repository->getOreAggregatedResourceByUri($relationSubjectUri);
				$relationSubjectTitle = $relationSubject->getTitle();
				
				$relationPredicateTitle = $this->repository->repositorySchema->getResourceLabel($relationPredicateUri);
				
				$relationObject = $this->repository->getOreAggregatedResourceByUri($relationObjectUri);
				$relationObjectTitle = $relationObject->getTitle();
				
				// keep in sync with displayRelationAnnotation on escape_repositoryui_RepositoryUi 
				$title = 'On "' . $relationSubjectTitle . '" ' . mb_strtolower($relationPredicateTitle) . ' "' . $relationObjectTitle . '"';
			}
			
			$this->titleTriple = array(
							's' => $this->getUri(),
							'P' => 'http://purl.utwente.nl/ns/escape-annotations.owl#title',
							'o' => $title,
						);

			if(!$returnAsTriple && $this->titleTriple !== null)
			{
				return $this->titleTriple['o'];
			}
			else
			{
				return $this->titleTriple;
			}
		}
		else
		{
			return parent::getTitle($returnAsTriple);
		}
	}

	/**
	 * Updates the semantic RDF statements with the RDF statements provided.
	 * @param simplerdf_RdfResource $properties RDF statements as an simplerdf_RdfResource object
	 * @return string Timestamp
	 */
	public function setProperties(&$properties)
	{
		parent::setProperties($properties);
		
		// make sure that the aggregation modifcation date is also updated whenever an aggregated resource is updated
		//TODO: replace with a touch() function? 
		$oreAggregation = $this->getAggregation();
		$rdfResource = $oreAggregation->getProperties();
		$oreAggregation->setProperties($rdfResource);
	}
	
	/**
	 * Fetch second degree related resources.
	 * @return array An array, keys containing object URIs, values containing arrays, keys containing 'title' and 'type'
	 */
	public function getSecondDegreeRelatedResources()
	{
		$repositorySchema =& $this->repository->repositorySchema;

		$currentResource = '<' . $this->tripleStore->sparqlEscapeUri($this->getUri()) . '>';

		// make a list of the direct related resources
		$query = 'select distinct ?related
where
{
	{ ' . $currentResource . ' ?p0 ?related . }
	union
	{ ?related ?p1 ' . $currentResource . ' . }
	?related <info:fedora/fedora-system:def/model#hasModel> <info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource> .
	filter ( isIRI(?related) && ?related != ' . $currentResource . ' )
}';
		
		//TODO: handle exceptions
		$sparqlResult = $this->tripleStore->sparqlSelect($query);
		$results = $sparqlResult->getResults();
		
		// store the result in an array
		$directRelatedResources = array();
		foreach($results as $result)
		{
			$directRelatedResources[] = $result['related']['value'];
		}
		$directRelatedResources = array_flip($directRelatedResources);
		
		// fetch second degree related resources
		$query = 'select distinct ?related2 ?type
where
{
	{ ' . $currentResource . ' ?p0 ?related1 . } union { ?related1 ?p0 ' . $currentResource . ' . }
	?related1 <info:fedora/fedora-system:def/model#hasModel> <info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource> .
	filter (?related1 != ' . $currentResource . ') .
	{ ?related1 ?p1 ?related2 . } union { ?related2 ?p1 ?related1 . }
	?related2 <info:fedora/fedora-system:def/model#hasModel> <info:fedora/' . $this->repository->config['system_object_namespace'] . ':cmodel.ore.AggregatedResource> .
	?related2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?type .
	filter (?related2 != ' . $currentResource . ')
}';
		
		//TODO: handle exceptions
		$sparqlResult = $this->tripleStore->sparqlSelect($query);
		$results = $sparqlResult->getResults();
		
		// store the result in an array
		$relatedResources = array();
		foreach($results as $result)
		{
			$uri = $result['related2']['value'];

			// only store related objects if they do not also occur as a directly related object
			if(!isset($directRelatedResources[$uri]))
			{
				$relatedResources[$uri]['type'] = $result['type']['value'];
			}
		}

		// fetch the object titles, depending on the repository schema definitions
		$varIdx = 0;
		$template = '';
		$queryGraphs = array();
		foreach($relatedResources as $uri => &$relatedResource)
		{
			$titleUris = $repositorySchema->getClassTitlePropertyUriList($relatedResource['type']);
			
			if(count($titleUris) > 0)
			{
				$titleUriGraphs = array();
				foreach($titleUris as $titleUri)
				{
					$titleUriGraphs[] = '{ <' . $this->tripleStore->sparqlEscapeUri($uri) . '> <' . $this->tripleStore->sparqlEscapeUri($titleUri) . '> ?title' . $varIdx . ' }';
				}
				$queryGraphs[] = "{" . implode(' union ', $titleUriGraphs) . "}";

				$template .= '<' . $this->tripleStore->sparqlEscapeUri($uri) . '> <http://purl.org/dc/terms/title> ?title' . $varIdx . " .\n";
				
				$varIdx++;
			}
		}
		$query = "construct\n";
		$query .= "{\n";
		$query .= $template;
		$query .= "}\n";
		$query .= "where\n";
		$query .= "{\n";
		$query .= implode("\nunion\n", $queryGraphs) . "\n";
		$query .= "}\n";
		
		$resultRdfDocument = $this->tripleStore->sparqlConstruct($query);
		
		$rdfResources = $resultRdfDocument->getResources();
		
		// store the titles in the result array, items without titles are dropped
		$relatedResourcesWithTitle = array();
		foreach($rdfResources as $rdfResouce)
		{
			$relatedResourcesWithTitle[$rdfResouce->getSubject()] = $relatedResources[$rdfResouce->getSubject()];
			$relatedResourcesWithTitle[$rdfResouce->getSubject()]['title'] = $rdfResouce->getFirstObject('http://purl.org/dc/terms/title');
		}
		
		return $relatedResourcesWithTitle;
	}
}
